from Exception_ import FgsException
import socket
try:
    import threading
except ImportError:
    import dummy_thread as threading

class Bridge(object):
    """FGS bridge class the bridges two sockets after FGS ident."""

    # Methods called by the parent thread, the one creating the bridge
    def __init__(self, server):
        self.server = server
        self.host = server.host
        self.port = server.port
        self.localport = server.game.port

        self._in_buf = ""
        self._out_buf = ""

        self._observers = []
        self._observers_lck = threading.Lock()
        self._shutdown_request = False
        self._shutdown_request_lck = threading.Lock()

        self.thread = None

    def add_observer(self, obs):
        self._observers_lck.acquire()
        if obs not in self._observers:
            self._observers.append(obs)
        self._observers_lck.release()

    def remove_observer(self, obs):
        self._observers_lck.acquire()
        if obs in self._observers:
            self._observers.remove(obs)
        self._observers_lck.release()

    def start_bridge(self):
        self.thread = threading.Thread(target=self._start_bridge,
                                       args=())
        self.thread.setDaemon(True)
        self.thread.start()

    def shutdown_bridge(self):
        self._shutdown_request_lck.acquire()
        self._shutdown_request = True
        self._shutdown_request_lck.release()

    #
    # Methods called by the bridge thread, when launched.
    #
    def _start_bridge(self):
        """Main bridge thread function. Initializes and starts
        relaying data from gamer to server."""

        try:
            # Connect to the game server
            self._init_client_cnx()
            if self._needs_shutdown():
                self._shutdown_bridge()
                return

            # Authenticate to the FGS-enabled server
            self._identify_client()
            if self._needs_shutdown():
                self._shutdown_bridge()
                return

            # Set up the server socket to relay the client data
            self._init_server_cnx()
            if self._needs_shutdown():
                self._shutdown_bridge()
                return

            # Go into the main loop and start relaying
            self._relay_bridge()
        except FgsException, e:
            self._observers_lck.acquire()
            for o in self._observers:
                o.bridge_error(e)
            self._observers_lck.release()

    def _needs_shutdown(self):
        """Return True if the controller thread requested a shutdown
        of the bridge thread."""
        if not self._shutdown_request_lck.acquire(0):
            return False
        shutdown = self._shutdown_request
        self._shutdown_request_lck.release()
        return shutdown

    def _init_client_cnx(self):
        self._observers_lck.acquire()
        for o in self._observers:
            o.init_client()
        self._observers_lck.release()

        try:
            self.client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client_sock.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self.client_sock.settimeout(10.0)
            self.client_sock.connect((self.host, self.port))
        except socket.timeout:
            raise FgsException("Timed out while connecting to game server.")
        except socket.error, e:
            raise FgsException("Error connecting to game server: %s" % e[1])

    def _identify_client(self):
        from sha import new

        self._observers_lck.acquire()
        for o in self._observers:
            o.identify_client()
        self._observers_lck.release()

        self._send_status_line('FGS CLIENT 0.1')
        salt = self._validate_status_line(wait_auth=True)

        hash_ = new(self.server.game.portal.user.sid + salt).hexdigest()
        self._send_status_line('%s %s' % (self.server.game.portal.user.name,
                                          hash_))
        self._validate_status_line()

    def _init_server_cnx(self):
        self._observers_lck.acquire()
        for o in self._observers:
            o.init_server()
        self._observers_lck.release()

        try:
            self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server_sock.setsockopt(socket.SOL_SOCKET,
                                        socket.SO_REUSEADDR, 1)
            self.server_sock.bind(("127.0.0.1", self.localport))
            self.server_sock.listen(1)
        except socket.error, e:
            raise FgsException("Error setting up local relay: %s" % e[1])

    def _relay_bridge(self):
        from select import select

        self._observers_lck.acquire()
        for o in self._observers:
            o.wait_for_gamer()
        self._observers_lck.release()

        self.gamer_sock = None
        while not self.gamer_sock:
            ifs, ofs, efs = select([self.server_sock], [], [], 1.0)
            if self._needs_shutdown():
                self._shutdown_bridge()
                return
            if self.server_sock in ifs:
                self.gamer_sock, addr = self.server_sock.accept()
                self.server_sock.close()

        self._observers_lck.acquire()
        for o in self._observers:
            o.bridge_active()
        self._observers_lck.release()

        # Send any data still left in-buffer from the game server
        if len(self._in_buf) > 0:
            self.gamer_sock.sendall(self._in_buf)

        socks = [self.client_sock, self.gamer_sock]

        while True:
            # Check for shutdown conditions
            if self._needs_shutdown():
                self._shutdown_bridge()
                return

            ifs, ofs, efs = select(socks, [], [], 1.0)

            for sock in ifs:
                try:
                    data = sock.recv(256)
                except socket.error, e:
                    self._shutdown_bridge()
                    return

                if len(data) == 0:
                    self._shutdown_bridge()
                    return

                idx = socks.index(sock)

                self._observers_lck.acquire()
                for o in self._observers:
                    o.send_data(data, idx)
                self._observers_lck.release()

                socks[1 - idx].sendall(data)

    def _shutdown_bridge(self):
        self._observers_lck.acquire()
        for o in self._observers:
            o.bridge_shutdown()
        self._observers_lck.release()

        try:
            self.client_sock.close()
        except:
            pass

        try:
            self.server_sock.close()
        except:
            pass

        try:
            self.gamer_sock.close()
        except:
            pass

    # Utility functions to communicate with the FGS enabled game
    # server for the preliminary authentication phase.
    def _send_status_line(self, line):
        """Write the given data to the client socket, looping until
        all data is sent."""
        '\n'.join(line.split(' '))
        line = line+"\n\0\n"
        try:
            self.client_sock.sendall(line, socket.TCP_NODELAY)
        except socket.timeout:
            raise FgsException("Timeout during data send")
        except socket.error, e:
            raise FgsException("Error sending data: %s", e[1])

    def _validate_status_line(self, wait_auth=False):
        """Check that the next line read from the socket reads
        OK. Otherwise, raise an error."""
        from re import match

        line = self._recv_status_line()
        if wait_auth and line.startswith("AUTH "):
            return line[5:]
        elif wait_auth:
            self.client_sock.close()
            raise FgsException("Waiting for auth, but got %s" % line)
        elif line == "OK":
            return True

        self.client_sock.close()
        m = match(r"FAILED ([0-9]+) (\w.*)", line)
        if m:
            raise FgsException("Error %s : %s" % (m.group(1),m.group(2)))
        else:
            raise FgsException("Protocol error\n"+
                               "got unknown sequence '%s'" % line)

    def _recv_status_line(self):
        """Read data from the client socket into the buffer until an \n
        is read, then return the read line (with \n stripped)."""
        from select import select

        # 10 seconds to receive replies to what we say
        timeout = 10

        while True:
            ifs, ofs, efs = select([self.client_sock], [], [], 1.0)
            if self._needs_shutdown():
                self._shutdown_bridge()
                import sys
                sys.exit()
            timeout -= 1
            if self.client_sock in ifs:
                try:
                    self._in_buf += self.client_sock.recv(256)
                except socket.error, e:
                    raise FgsException("Error receiving data: %s", e[1])
                if '\0\n' in self._in_buf:
                    line, self._in_buf = self._in_buf.split('\0\n', 1)
                    return ' '.join(line.split("\n"))
            elif timeout == 0:
                raise FgsException("Timeout during identification sequence")

class BridgeObserver(object):
    def __init__(self, bridge):
        self.bridge = bridge
        bridge.add_observer(self)

    def init_client(self):
        pass

    def identify_client(self):
        pass

    def init_server(self):
        pass

    def wait_for_gamer(self):
        pass

    def bridge_active(self):
        pass

    def send_data(self, data, is_downstream):
        pass

    def bridge_error(self, exc):
        pass

    def bridge_shutdown(self):
        pass
